using System;
using System.Collections.Generic;
using UnityEngine;
using Framework.Core;

namespace Framework
{
    internal class ManagerContext
    {
        public object sign;
    }
    public class EntityManager
    {
        private AssetLoader _loader;
        private Transform _container;
        private object _sign;
        private Queue<EntityLogic> _addList;
        private List<EntityLogic> _loadList;
        private List<EntityLogic> _workList;
        private CustomPool<GameObjectLoadItem> _loadPool;
        private Dictionary<string, GameObjectLoadItem> _loadItem;
        private Dictionary<string, Transform> _groupContainer;
        private Dictionary<string, int> _assetPoolMax;
        private int instanceID;
        private List<EntityLogic> _clearList;
        private LogicPoolMgr logicPoolMgr;
        private float _createInstancePreFrame;
        private float _createInstanceCount;
        private ManagerContext _context;
        public void SetInstanceCountPreFrame(float count = 100f)
        {
            _createInstancePreFrame = count;
            _createInstanceCount = 0;
        }
        public EntityManager(Transform container, AssetLoader loader)
        {
            _groupContainer = new Dictionary<string, Transform>();
            _container = container;
            this._loader = loader;
            _addList = new Queue<EntityLogic>(100);
            _loadList = new List<EntityLogic>(100);
            _workList = new List<EntityLogic>(100);
            _loadPool = new CustomPool<GameObjectLoadItem>(() => { return new GameObjectLoadItem(); });
            _loadItem = new Dictionary<string, GameObjectLoadItem>(StringComparer.Ordinal);
            _assetPoolMax = new Dictionary<string, int>(StringComparer.Ordinal);
            _sign = _groupContainer;
            instanceID = 1;
            logicPoolMgr = new LogicPoolMgr();
            _createInstancePreFrame = 100f;
            _createInstanceCount = 0;
            _context = new ManagerContext();
            _context.sign = _sign;
        }
        public void Update(float deltaTime, float unscaledDeltaTime)
        {
            var len = _addList.Count;
            while (len-- > 0)
            {
                var logic = _addList.Dequeue();
                if (logic.Removing)
                {
                    DestroyLogic(logic);
                    continue;
                }
                if (!logic.IsGameObjectEnabled)
                {
                    _loadList.Add(logic);
                    if (!_loadItem.TryGetValue(logic.Path, out GameObjectLoadItem loadItem))
                    {
                        loadItem = _loadPool.Get();
                        _loadItem.Add(logic.Path, loadItem);
                        var loadResult = _loader.LoadAsset(logic.Path, loadItem.OnLoaded);
                        loadResult.IsGameObject = false;
                    }
                    continue;
                }
                _workList.Add(logic);
                logic.LogicStart(_sign);
            }
            len = _loadList.Count;
            if (_createInstanceCount < 1000000)
            {
                _createInstanceCount += _createInstancePreFrame;
            }
            while (len-- > 0)
            {
                var item = _loadList[len];
                if (item.Removing)
                {
                    _loadList.RemoveAt(len);
                    DestroyLogic(item);
                    continue;
                }
                if (_createInstanceCount > 0)
                {
                    if (_loadItem.TryGetValue(item.Path, out GameObjectLoadItem loadItem))
                    {
                        if (loadItem.Finished)
                        {
                            var typeName = item.GetType().FullName;
                            if (!_groupContainer.TryGetValue(typeName, out Transform container))
                            {
                                var go = new GameObject(typeName);
                                container = go.transform;
                                container.SetParent(_container, false);
                                _groupContainer.Add(typeName, container);
                            }
                            if (loadItem.CreateInstance(container))
                            {
                                _createInstanceCount -= 1f;
                                item.SetGameObject(_sign, loadItem.Value, container);
                                var watch = loadItem.Value.AddComponent<DestroyWatcher>();
                                watch.Logic = item;
                                watch.Callback = OnGameObjectDestroy;
                                loadItem.Value = null;
                                _loadList.RemoveAt(len);
                                _workList.Add(item);
                                item.LogicStart(_sign);
                            }
                            else
                            {
                                Remove(item);
                            }
                        }
                    }
                }

            }
            len = _workList.Count;
            while (len-- > 0)
            {
                var item = _workList[len];
                if (item.Removing)
                {
                    _workList.RemoveAt(len);
                    DestroyLogic(item);
                    continue;
                }
                if (item.Parent == null)
                {
                    item.Update(_sign, deltaTime, unscaledDeltaTime);
                }
            }
        }
        public void FixedUpdate(float deltaTime, float unscaledDeltaTime)
        {
            var len = _workList.Count;
            while (len-- > 0)
            {
                var item = _workList[len];
                if (!item.FixedUpdateEnabled)
                {
                    continue;
                }
                if (item.Removed)
                {
                    continue;
                }
                if (item.Removing)
                {
                    continue;
                }
                if (item.Parent == null)
                {
                    item.FixedUpdate(_sign, deltaTime, unscaledDeltaTime);
                }
            }
        }

        public T Add<T>(string assetPath) where T : EntityLogic, new()
        {
            if (string.IsNullOrEmpty(assetPath))
            {
                return null;
            }
            var logic = (T)PopLogic<T>(assetPath);
            if (logic != null)
            {
                if (string.IsNullOrEmpty(logic.Path))
                {
                    logic.LogicAwake(_sign, assetPath, instanceID++);
                }
                logic.Removing = false;
                _addList.Enqueue(logic);
            }
            return logic;
        }
        public void Remove(EntityLogic value)
        {
            if (value == null) return;
            if (string.IsNullOrEmpty(value.Path)) return;

            value.SetRemoveing(_sign);
        }
        public void GetEntitys<T>(List<T> result) where T : EntityLogic
        {
            var type = typeof(T);
            var len = _workList.Count;
            while (len-- > 0)
            {
                var item = _workList[len];
                if (item.Type == type)
                {
                    result.Add((T)item);
                }
            }
        }
        public void Clear()
        {
            var len = _addList.Count;
            while (len-- > 0)
            {
                var logic = _addList.Dequeue();
                DestroyLogic(logic);
            }
            len = _loadList.Count;
            while (len-- > 0)
            {
                var logic = _loadList[len];
                DestroyLogic(logic);
            }
            _loadList.Clear();
            len = _workList.Count;
            while (len-- > 0)
            {
                var logic = _workList[len];
                DestroyLogic(logic);
            }
            _workList.Clear();
        }
        public PoolInfo GetPoolInfo<T>(string assetPath)
        {
            Type logicType = typeof(T);
            var pool = logicPoolMgr.GetPool(logicType, assetPath);
            return pool.info;
        }
        public void ClearPool<T>(string assetPath, int count)
        {
            Type logicType = typeof(T);
            var pool = logicPoolMgr.GetPool(logicType, assetPath);
            count = Mathf.Min(count, pool.info.UnusedCount);
            if (count > 0)
            {
                if (_clearList == null)
                {
                    _clearList = new List<EntityLogic>(32);
                }
                pool.Fetch(count, _clearList);
                count = _clearList.Count;
                while (count-- > 0)
                {
                    var logic = _clearList[count];
                    logic.LogicDestroy(_sign);
                }
                _clearList.Clear();
            }
        }
        private void DestroyLogic(EntityLogic logic, bool fromWatcher = false)
        {
            if (logic == null)
            {
                return;
            }
            if (logic.Removed)
            {
                return;
            }
            logic.LogicRemove(_sign);
            PushLogic(logic);
        }
        private EntityLogic PopLogic<T>(string assetPath) where T : EntityLogic, new()
        {
            if (string.IsNullOrEmpty(assetPath))
            {
                return null;
            }
            Type logicType = typeof(T);
            var pool = logicPoolMgr.GetPool(logicType, assetPath);
            pool.Context = this._context;
            pool.Pop(out var ins);
            if (ins != null)
            {
                return ins;
            }
            var logic = new T();
            logic.SetLogicType(logicType);
            pool.info.Pop();
            return logic;
        }
        private void PushLogic(EntityLogic logic)
        {
            var logicType = logic.GetType();
            var pool = logicPoolMgr.GetPool(logic.Type, logic.Path);
            pool.Push(logic);
        }
        private void OnGameObjectDestroy(EntityLogic logic)
        {
            if (!logic.IsGameObjectEnabled) { return; }
            logic.SetGameObject(_sign);
            DestroyLogic(logic, true);
        }
        private class GameObjectLoadItem
        {
            public bool Finished { get; private set; }
            public GameObject Value;
            private GameObject _asset;
            private string Name;
            public void OnLoaded(IAssetResult result)
            {
                _asset = result.Asset as GameObject;
                this.Name = System.IO.Path.GetFileNameWithoutExtension(result.Path);
                this.Finished = true;
            }
            public bool CreateInstance(Transform parent)
            {
                Value = null;
                if (!Finished) return false;
                if (_asset == null) return false;
                var ins = GameObject.Instantiate(_asset);
                GameObject.DontDestroyOnLoad(ins);
                ins.SetActive(true);
                ins.name = Name;
                if (ins.transform.parent != parent)
                {
                    ins.transform.SetParent(parent, false);
                }
                Value = ins;
                return Value != null;
            }
        }

        internal class ActionData
        {
            public bool Add;
            public EntityLogic Value;
        }
        internal class DestroyWatcher : MonoBehaviour
        {
            public EntityLogic Logic;
            public Action<EntityLogic> Callback;
            private void OnDestroy()
            {
                Callback?.Invoke(Logic);
            }
        }
        internal class LogicPoolMgr
        {
            private Dictionary<string, List<LogicPool>> _pathMap = new Dictionary<string, List<LogicPool>>(100, StringComparer.Ordinal);
            private bool pathCountWarning;
            private bool typeCountWarning;
            private LogicPool _latestPool;
            private string _latestPath;
            private Type _latestType;
            public LogicPoolMgr()
            {

            }
            public LogicPool GetPool(Type type, string path)
            {
                if (_latestPath == path && _latestType == type)
                {
                    return _latestPool;
                }
                if (pathCountWarning && _pathMap.Count > 1000)
                {
                    FrameworkLog.LogError("assets count more than 1000");
                    pathCountWarning = false;
                }
                if (!_pathMap.TryGetValue(path, out var list))
                {
                    list = new List<LogicPool>(10);
                    _pathMap.Add(path, list);
                }
                if (typeCountWarning && list.Count > 1000)
                {
                    FrameworkLog.LogError("type count more than 1000");
                    typeCountWarning = false;
                }
                _latestType = type;
                _latestPath = path;
                var len = list.Count;
                while (len-- > 0)
                {
                    _latestPool = list[len];
                    if (_latestPool.info.TargetType == type)
                    {
                        return _latestPool;
                    }
                }
                _latestPool = new LogicPool(type, 32);
                list.Add(_latestPool);
                return _latestPool;
            }
        }
        internal class LogicPool
        {
            public ManagerContext Context;
            public EditPoolInfo info;
            private Queue<EntityLogic> list;
            public LogicPool(Type type, int count = 32)
            {
                info = new EditPoolInfo(type);
                list = new Queue<EntityLogic>(count);
            }
            public void Push(EntityLogic value)
            {
                list.Enqueue(value);
                info.Push(1);
                info.Pop(-1);
            }
            public void Pop(out EntityLogic value)
            {
                value = null;
                var len = list.Count;
                if (len < 1)
                {
                    return;
                }
                while (len-- > 0)
                {
                    value = list.Dequeue();
                    if (value.Removing)
                    {
                        continue;
                    }
                    if (value.IsGameObjectEnabled && !value.GetGameObjectEnabled(Context.sign))
                    {
                        value = null;
                        continue;
                    }
                    info.Push(-1);
                    info.Pop(1);
                    break;
                }
            }
            public void Fetch(int count, List<EntityLogic> target)
            {
                count = Mathf.Min(count, list.Count);
                while (count-- > 0)
                {
                    var logic = list.Dequeue();
                    info.Push(-1);
                    target.Add(logic);
                }
            }
        }
    }
}